#ifndef HIR_GRAPHBUILDER_HPP
#define HIR_GRAPHBUILDER_HPP
#pragma once

#include <list>
#include <vector>
#include <memory>
#include <tuple>
#include <algorithm>
#include <bitset>
#include <unordered_map>
namespace hir
{

class Instruction;
class InstrStorage;

constexpr unsigned MAX_BASIC_BLOCK_ID = 1024;

struct PhiDesc
{
    uint32_t index_;
    std::vector<uint32_t> from_bci_;
    std::vector<uint32_t> from_parent_;
};

class BasicBlock
{
public:
    explicit BasicBlock(uint32_t id,uint32_t bci_start) : id_(id)
        , bci_start_(bci_start)
        , bci_end_(-1)
        , dominator_(NULL)
        , isLoopHeader_(false)
        , isExceptHander_(false)
        , isFinallyHandler_(false)
    {
    }

    typedef std::list<BasicBlock *> BlockList;
    typedef std::vector<PhiDesc> PhiDescList;

    void setSuccessor(BasicBlock * other)
    {
        other->pred_.push_back(this);
        this->succ_.push_back(other);
    }

    inline BasicBlock * dominator() const
    {
        return this->dominator_;
    }

    void dominate(BasicBlock * other)
    {
        DEBUG_ASSERT(other->dominator() == NULL); // target bb's dominator must be null when enter this method
        other->dominator_ = this;
        this->dominatee().push_back(other);
    }

    BasicBlock * findCommonDominator(const BasicBlock * bb)
    {
        BasicBlock * current = this;
        while(!current->dominates(bb))
        {
            current = current->dominator_;
        }
        return current;
    }

    // dominate target bb if bb has not been dominated
    // or find common dominator to dominate the target
    void dominateFindUpper(BasicBlock * bb);

    // release the dominate relationship
    void freeDominator();
    // release predecessor
    void freePredecessor(BasicBlock * bb);

    bool dominates(const BasicBlock * bb) const;
    void addPhiFor(int index,uint32_t bci);
    void addPhiFromPhi(int index,int bbId);

    inline uint32_t id() const
    {
        return id_;
    }
    inline uint32_t bciStart() const
    {
        return bci_start_;
    }
    inline uint32_t bciEnd() const
    {
        return bci_end_;
    }
    inline BlockList & dominatee()
    {
        return dominatee_;
    }
    inline BlockList & successor()
    {
        return succ_;
    }
    inline BlockList & predecessor()
    {
        return pred_;
    }
    inline bool isEnd(uint32_t bci) const
    {
        return bci_end_ <= bci;
    }
    inline bool isLoopHeader() const
    {
        return isLoopHeader_;
    }
    inline bool isExceptHandler() const
    {
        return isExceptHander_;
    }
    inline bool isFinallyHandler() const
    {
        return isFinallyHandler_;
    }
    inline bool isEndBlock() const
    {
        return bci_start_ == -1 && bci_end_ == -1 ;
    }
    inline void setEnd(uint32_t bci)
    {
        bci_end_ = bci;
    }
    inline void markLoopHeader()
    {
        isLoopHeader_ = true;
    }
    inline void unmarkLoopHeader()
    {
        isLoopHeader_ = false;
    }
    inline void markExceptHandler()
    {
        isExceptHander_ = true;
    }
    inline void markFinallyHandler()
    {
        isFinallyHandler_ = true;
    }
    PhiDescList & phiDescList()
    {
        return phi_desc_list_;
    }

    void print(int (*logFunc)(const char *,...));
private:
    BlockList pred_;
    BlockList succ_;
    BasicBlock * dominator_;
    BlockList dominatee_;
    PhiDescList phi_desc_list_;

    uint32_t id_;
    uint32_t bci_start_;
    uint32_t bci_end_;

    bool isLoopHeader_: 1;
    bool isExceptHander_:1;
    bool isFinallyHandler_:1;

};

// helper to build phi
class LocalDesc
{
public:
    constexpr static int MAX_LOCAL_COUNT = 64;
    typedef std::bitset<MAX_LOCAL_COUNT > LocalBitmap;

    bool recordLocalStore(int index,uint32_t bci);
    uint32_t getLastStoreLocation(int index);
    inline bool isOutputSet(int index)
    {
        return outputBitmap_.test(index);
    }
private:
    LocalBitmap outputBitmap_;
    std::unordered_map<uint32_t /*local index*/,uint32_t/*bci*/> storeLocation_;
};

// the data structure graph builder is desinated to store nodes , manipulate  relationship
class GraphBuilder
{
public:
    typedef std::list<std::unique_ptr<BasicBlock> > BlockList;

    explicit GraphBuilder(InstrStorage * storage) : storage_(storage) {}

    uint32_t loadConst(uint32_t oparg,uint32_t bci);
    uint32_t loadFast(uint32_t oparg,uint32_t bci);
    void storeFast(uint32_t operand,uint32_t oparg,uint32_t bci);
    uint32_t unaryPositive(uint32_t operand,uint32_t bci);
    uint32_t unaryNegative(uint32_t operand,uint32_t bci);
    uint32_t unaryNot(uint32_t operand,uint32_t bci);
    uint32_t unaryConvert(uint32_t operand,uint32_t bci);
    uint32_t unaryInvert(uint32_t operand,uint32_t bci);
    uint32_t binaryPower(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryMultiply(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryDivide(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryTrueDivide(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryFloorDivide(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryModulo(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryAdd(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binarySubtract(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binarySubscr(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryLshift(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryRshift(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryAnd(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryXor(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t binaryOr(uint32_t operand1,uint32_t operand2,uint32_t bci);
    void listAppend(uint32_t operand1,uint32_t operand2,uint32_t bci);
    void setAdd(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t slice(uint32_t operand1,uint32_t operand2,uint32_t operand3,uint32_t bci);
    uint32_t deleteSlice(uint32_t operand1,uint32_t operand2,uint32_t operand3,uint32_t bci);
    uint32_t storeSubscr(uint32_t operand1,uint32_t operand2,uint32_t operand3,uint32_t bci);
    uint32_t storeSlice(uint32_t operand1,uint32_t operand2,uint32_t operand3,uint32_t operand4,uint32_t bci);
    void deleteSubscr(uint32_t operand1,uint32_t operand2,uint32_t bci);
    void printExpr(uint32_t operand,uint32_t bci);
    void printItemTo(uint32_t stream,uint32_t obj,uint32_t bci);
    void printItem(uint32_t obj,uint32_t bci);
    void printNewlineTo(uint32_t obj,uint32_t bci);
    void printNewline(uint32_t bci);
    void raiseVarargs(uint32_t type, uint32_t value, uint32_t tb,uint32_t bci);
    uint32_t loadLocals(uint32_t bci);
    void returnValue(uint32_t retval,uint32_t bci);
    void yieldValue(uint32_t retval,uint32_t bci);
    void execStatment(uint32_t prog, uint32_t globals, uint32_t locals,uint32_t bci);
    void popBlock(uint32_t bci);
    void endFinally(uint32_t bci);
    uint32_t buildClass(uint32_t methods, uint32_t bases, uint32_t name,uint32_t bci);
    void storeName(uint32_t value,uint32_t oparg,uint32_t bci);
    void deleteName(uint32_t oparg,uint32_t bci);
    uint32_t unpackSequence(uint32_t tuple,uint32_t index,uint32_t bci);
    void storeAttr(uint32_t object,uint32_t value,uint32_t opargName,uint32_t bci);
    void deleteAttr(uint32_t object,uint32_t opargName,uint32_t bci);
    void storeGlobal(uint32_t value,uint32_t opargName,uint32_t bci);
    void deleteGlobal(uint32_t opargName,uint32_t bci);
    uint32_t loadName(uint32_t opargName,uint32_t bci);
    uint32_t loadGlobal(uint32_t opargName,uint32_t bci);
    void deleteFast(uint32_t opargName,uint32_t bci);
    uint32_t loadClosure(uint32_t opargName,uint32_t bci);
    uint32_t loadDeref(uint32_t opargName,uint32_t bci);
    void storeDeref(uint32_t objectOperand,uint32_t opargName,uint32_t bci);
    uint32_t buildTuple(uint32_t opargCount,uint32_t * operands,uint32_t bci);
    uint32_t buildList(uint32_t opargCount,uint32_t * operands,uint32_t bci);
    uint32_t buildSet(uint32_t opargCount,uint32_t * operands,uint32_t bci);
    uint32_t buildMap(uint32_t opargCount,uint32_t bci);
    void storeMap(uint32_t operand1,uint32_t operand2, uint32_t operand3,uint32_t bci);
    void mapAdd(uint32_t operand1,uint32_t operand2,uint32_t operand3,uint32_t bci);
    uint32_t loadAttr(uint32_t operand1,uint32_t oparg,uint32_t bci);
    uint32_t compareOp(uint32_t operand1,uint32_t operand2,uint32_t oparg,uint32_t bci);
    uint32_t importName(uint32_t operand1,uint32_t operand2,uint32_t oparg,uint32_t bci);
    void importStar(uint32_t operand1,uint32_t bci);
    uint32_t importFrom(uint32_t operand1,uint32_t oparg,uint32_t bci);
    void jumpForward(uint32_t oparg,uint32_t bci);
    void jumpIfTrue(uint32_t objectOperand,uint32_t oparg,uint32_t bci);
    void jumpIfFalse(uint32_t objectOperand,uint32_t oparg,uint32_t bci);
    uint32_t getIter(uint32_t operand,uint32_t bci); // common stub needed
    uint32_t forIter(uint32_t iterObject,uint32_t bciToJumpWhenExit,uint32_t bci); // common stub needed
    void breakLoop(uint32_t bci);
    // decompose setup with to two ssa inst : WITH_EXIT WITH_ENTER
    std::tuple<uint32_t,uint32_t> compileSetupWith(uint32_t operand, uint32_t bci);
    void withCleanup(uint32_t operand1/*exit func*/,uint32_t operand2,uint32_t operand3,uint32_t operand4,uint32_t bci);
    // need to see how it eval first
    uint32_t callFunc(uint32_t bci);
    uint32_t makeFunc(uint32_t codeOperand,uint32_t * operands,uint32_t operandsCount,uint32_t bci);
    uint32_t makeClosure(uint32_t codeOperand,uint32_t closureOperand,uint32_t * operands,uint32_t operandsCount,uint32_t bci);
    uint32_t buildSlice(uint32_t operand1,uint32_t operand2,uint32_t operand3,uint32_t bci);

    uint32_t inplaceAdd(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceSubtract(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceMultiply(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceDivide(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceModulo(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplacePower(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceLshift(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceRshift(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceAnd(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceXor(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceOr(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceTrueDivide(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t inplaceFloorDivide(uint32_t operand1,uint32_t operand2,uint32_t bci);
    uint32_t addConst(uint32_t index,PyObject * o);
    uint32_t addPhi(uint32_t index);

protected:
    uint32_t commonAppend(std::unique_ptr<Instruction> & tmp,uint32_t bci);

    uint32_t commonAddUse(uint32_t beUsed,uint32_t user);

private:
    // this storage initFields before actual call to its interfaces
    InstrStorage * storage_;
};

}

#endif /* HIR_GRAPHBUILDER_HPP */
